package com.hmdp.utils;

import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
@Component
public class RedisIdWorker {
    //这个便是我们的全局id生成器，解决的问题就是把id更复杂化，不易让它们猜测获取相关数据
    /*这个id生成器的生成的id组成可以分成三大部分，符号位，时间戳，序列号
    * 符号位恒为0，表示为正数
    * 时间戳：结果是当前时间和我们设定的开始时间的数值相减得到
    * 序列号：结果是通过redis的String类型value的自增长特性来获取序列号，这个特性我测试了一下，在这里
    * 可以好好分析一下，就是存入redis中如果设置了自增，我们可以不用指定参数，就是只要它的key是一样的，每次
    * 添加到redis中，这个都会自增1，当key不同时，那就等于插入新的数据，从这个key由1又开始自增
    * */
    private static final Long BEGIN_TIME=1682121600l; //这个是我们设置的开始时间，相对于1970年1月1日的秒数
    private StringRedisTemplate stringRedisTemplate; //要用到redis的自增生成序列号
    private static final Long COUNT_BITS=32l;//设置拼接时，时间戳往左进位的位数

    public RedisIdWorker(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    public Long getId(String keyPrefix){
        //因为要区分，不同业务的不同数据封装的id要有所区分，所以在调用我们的全局id生成器时，
        //也要指定时给那类数据生成的id，方便我们今后维护时查看
        //todo:获取当前时间，转换成与原始时间（1970年1月1日）相差多少秒，和我们的开始时间相减得时间戳
        long nowTime = LocalDateTime.now().toEpochSecond(ZoneOffset.UTC);
        Long timeStamp=nowTime- BEGIN_TIME;

        //todo:获取序列号，用到redis来自增生成
        LocalDateTime now = LocalDateTime.now();
        //我们来自定义时间格式，方便组合成我们在redis中的key的时候，分组更好分
        String date = now.format(DateTimeFormatter.ofPattern("yyyy:mm:dd"));
        /*
        * 为什么这里的key要这么设计呢？我们先要明确一个概念，就是在redis中存入的数据假设存入了一个
        * key为 test：nihao  和一个key为 test：hello，这两个数据，在redis中会根据“：”把这两数据分到一个叫test的组里，
        * 因test这个字段是它们共有的，所以redis认为两者是有共性的，所以放到一个组里好管理。
        * 为什么还要在 key后加入 date？------>因在redis中，一个条String的value自增是有上限的，如果我们不加上date的话，
        * 也就是String key="icr:"+keyPrefix ,那么以后的这个业务的数据id都是在这里完成自增后得到了，久而久之就会达到
        * 这个上限，而加了一个date，每天有的date是不一样的，也就是到明天后，就是一个新的key，又从1开始递增，等于上限又刷新了
        * */

        String key="icr:"+keyPrefix+":"+date;
        Long increment = stringRedisTemplate.opsForValue().increment(key);//这个返回结果即使自增的值

        //todo:将时间戳和序列号进行拼接
        /*
        * 通过时间戳左移进位让出指定位数给序列号，序列号和移位后的时间戳或运算一下，移位后低位都是0，
        * 而序列号是值的，他的二进制位就存在若干个1，或运算一下，1|0=1，就直接把序列号加进低位了
        * */
        long finalId = timeStamp << COUNT_BITS | increment;

        return timeStamp;
    }
}
